package compilers.AST.expression;

import java.util.Iterator;
import java.util.LinkedList;

import compilers.AST.Node;
import compilers.AST.types.MyType;
import compilers.AST.types.TypeSpecifier;
import compilers.compiler.PGenerator;
import compilers.exceptions.SemanticalException;
import compilers.symbolTable.SymbolTableScope;

public class Sum extends Node {
	
	public final static int PLUS = 1;
	public final static int MINUS = 2;

	private Mult fFirstMult;
	private LinkedList<Mult> fSecondMults;
	private LinkedList<Integer> fOperators;
	
	public Sum() {
		fOperators = new LinkedList<Integer>();
		fSecondMults = new LinkedList<Mult>();
	}
	
	public Sum(int lineNr) {
		super(lineNr);
		fOperators = new LinkedList<Integer>(); 
		fSecondMults = new LinkedList<Mult>();
	}
	
	public void print(int identLevel) {
		println(identLevel, "Sum");
		fFirstMult.print(identLevel+1);
		if (!fOperators.isEmpty()) {
			Iterator<Integer> itOperators = fOperators.iterator();
			Iterator<Mult> itMult = fSecondMults.iterator();
			while (itOperators.hasNext()) {
				int op = itOperators.next();
				Mult mult = itMult.next();
				String opName = "";
				switch (op) {
					case PLUS:
						opName = "+";
						break;
					case MINUS:
						opName = "-";
						break;
				}
				println(identLevel+1, "Operation = " + opName);
				mult.print(identLevel+1);
			}
		}
	}
	
	public void checkSemantics(SymbolTableScope currentScope) throws SemanticalException {
		// Check firstMult
		fFirstMult.checkSemantics(currentScope);
		
		// Check other mults
		if (!fOperators.isEmpty()) {
			if ((fFirstMult.getResultType(currentScope) != TypeSpecifier.INTEGER) && (fFirstMult.getResultType(currentScope) != TypeSpecifier.FLOAT)) {
				throw new SemanticalException("The type in the sum expression is not of the requested numerical types.", fLineNr);

			}
			Iterator<Mult> itMult = fSecondMults.iterator();
			while (itMult.hasNext()) {
				Mult mult = itMult.next();
				mult.checkSemantics(currentScope);
				if ((mult.getResultType(currentScope) != TypeSpecifier.INTEGER) && (mult.getResultType(currentScope) != TypeSpecifier.FLOAT)) {
					throw new SemanticalException("The type in the sum expression is not of the requested numerical types.", fLineNr);

				}
			}
		}
	}
	
	public void addOperator(int operation) {
		fOperators.add(operation);
	}
	
	public void setFirstMult(Mult mult) {
		fFirstMult = mult;
	}
	
	public void addSecondMult(Mult mult) {
		fSecondMults.add(mult);
	}
	
	public int getResultType(SymbolTableScope currentScope) {
		if (!fOperators.isEmpty()) {
			if (fFirstMult.getResultType(currentScope) == TypeSpecifier.FLOAT) {
				return TypeSpecifier.FLOAT;
			}
			Iterator<Mult> itMult = fSecondMults.iterator();
			while (itMult.hasNext()) {
				Mult mult = itMult.next();
				if (mult.getResultType(currentScope) == TypeSpecifier.FLOAT) {
					return TypeSpecifier.FLOAT;
				}
			}
			return TypeSpecifier.INTEGER;
		} else {
			return fFirstMult.getResultType(currentScope);
		}
	}
	
	public int getOriginalType(SymbolTableScope currentScope) {
		if (!fOperators.isEmpty()) {
			if (fFirstMult.getOriginalType(currentScope) == TypeSpecifier.FLOAT) {
				return TypeSpecifier.FLOAT;
			}
			Iterator<Mult> itMult = fSecondMults.iterator();
			while (itMult.hasNext()) {
				Mult mult = itMult.next();
				if (mult.getOriginalType(currentScope) == TypeSpecifier.FLOAT) {
					return TypeSpecifier.FLOAT;
				}
			}
			return TypeSpecifier.INTEGER;
		} else {
			return fFirstMult.getOriginalType(currentScope);
		}
	}
	
	public boolean isConstant(SymbolTableScope currentScope) {
		if(fOperators.isEmpty()) {
			return fFirstMult.isConstant(currentScope);
		} else {
			if (!fFirstMult.isConstant(currentScope)) {
				return false;
			}
			Iterator<Mult> itMult = fSecondMults.iterator();
			while (itMult.hasNext()) {
				Mult mult = itMult.next();
				if (!mult.isConstant(currentScope)) {
					return false;
				}
			}
			return true;
		}
	}
	

	public boolean isArray(SymbolTableScope currentScope) {
		if (fOperators.isEmpty()) {
			return fFirstMult.isArray(currentScope);
		} else {
			return false;
		}
	}
	
	public String getArrayIdent(SymbolTableScope currentScope) {
		return fFirstMult.getArrayIdent(currentScope);
	}
	
	public MyType calculateValue(SymbolTableScope currentScope) {
		if(fOperators.isEmpty()) {
			return fFirstMult.calculateValue(currentScope);
		} else {
			Iterator<Integer> itOp = fOperators.iterator();
			Iterator<Mult> itMult = fSecondMults.iterator();
			MyType result = fFirstMult.calculateValue(currentScope);
			while (itMult.hasNext()) {
				Mult mult = itMult.next();
				if(itOp.next() == PLUS) {
					result = result.plus(mult.calculateValue(currentScope));
				} else {
					result = result.minus(mult.calculateValue(currentScope));
				}
			}
			return result;
		}
	}
	
	public void generatePcode(PGenerator pGen, SymbolTableScope currentScope) {
		// Get resulttype and cast every term to this type if necessary
		int resultType = this.getResultType(currentScope);
		
		// First mult
		if (fFirstMult.isConstant(currentScope)) {
			TypeSpecifier firstResultType = new TypeSpecifier(fFirstMult.getResultType(currentScope));
			pGen.load(firstResultType.getPType(currentScope), fFirstMult.calculateValue(currentScope).valueToString());
		} else {
			fFirstMult.generatePcode(pGen, currentScope);
		}
		if (fFirstMult.getResultType(currentScope) != resultType) {
			pGen.conv(TypeSpecifier.getPType(fFirstMult.getOriginalType(currentScope)), TypeSpecifier.getPType(resultType));
		}
		
		// Second mults
		if (!fOperators.isEmpty()) {
			Iterator<Integer> itOp = fOperators.iterator();
			Iterator<Mult> itMult = fSecondMults.iterator();
			while (itMult.hasNext()) {
				Mult mult = itMult.next();
				if (mult.isConstant(currentScope)) {
					pGen.load(TypeSpecifier.getPType(mult.getResultType(currentScope)), mult.calculateValue(currentScope).valueToString());
				} else {
					mult.generatePcode(pGen, currentScope);
				}
				if (mult.getResultType(currentScope) != resultType) {
					pGen.conv(TypeSpecifier.getPType(mult.getOriginalType(currentScope)), TypeSpecifier.getPType(resultType));
				}
				if (itOp.next() == PLUS) {
					pGen.add(TypeSpecifier.getPType(resultType));
				} else {
					pGen.sub(TypeSpecifier.getPType(resultType));
				}
			}
		}
	}

}
